package com.xlh.support.backup.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import com.xlh.exception.common.GlobalException;
import com.xlh.support.backup.dao.BackupMapper;
import com.xlh.support.backup.dao.helper.BackupMapperHelper;
import com.xlh.support.backup.domain.Backup;
import com.xlh.support.backup.properties.BackupProperties;
import com.xlh.support.backup.service.BackupService;
import com.xlh.support.backup.vo.AutoBackupVO;
import com.xlh.support.backup.vo.BackupVO;
import com.xlh.util.SshUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Date;


/**
 * @author cheer
 */
@Service
@Slf4j
public class BackupServiceImpl implements BackupService {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Autowired
    private BackupMapper backupMapper;

    @Autowired
    private BackupMapperHelper backupMapperHelper;

    @Autowired
    private BackupProperties backupProperties;

    private volatile Boolean isRecover = false;

    private volatile Boolean isBackup = false;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Override
    public BackupVO queryAutoBackupSwitch() {
        BackupVO backupVO = BackupVO.builder().build();

        Backup backup = backupMapperHelper.getBackup();
        if (backup != null) {
            backupVO.setIsAutoBackup(backup.getAutoBackup());
            if (backup.getLatestBackupTime() != null) {
                backupVO.setLatestBackupDate(DateUtil.format(backup.getLatestBackupTime(), "yyyy/MM/dd HH:mm"));
            }
        } else {
            backupVO.setIsAutoBackup(false);
        }

        backupVO.setBackupServer(backupProperties.getServer());
        backupVO.setBackupDir(backupProperties.getDir());

        backupVO.setIsRecover(isRecover);
        if (isRecover) {
            backupVO.setRecoverStatus("正在恢复，请稍后刷新浏览器查看结果");
        } else if (backup != null && backup.getLatestRecoverTime() != null) {
            backupVO.setRecoverStatus("最新恢复完成日期：" + DateUtil.format(backup.getLatestRecoverTime(), "yyyy/MM/dd HH:mm"));
        } else {
            backupVO.setRecoverStatus("");
        }
        backupVO.setIsBackup(isBackup);
        if (isBackup) {
            backupVO.setBackupStatus("正在备份，请稍后刷新浏览器查看结果");
        } else if (backup != null && backup.getLatestBackupTime() != null) {
            backupVO.setBackupStatus("最新备份完成日期：" + DateUtil.format(backup.getLatestBackupTime(), "yyyy/MM/dd HH:mm"));
        } else {
            backupVO.setBackupStatus("");
        }

        return backupVO;
    }

    @Override
    public AutoBackupVO updateAutoBackupSwitch(Boolean switchOn) {
        Backup backup = backupMapperHelper.getBackup();
        if (backup == null) {
            Backup insertBackup = new Backup();
            insertBackup.setAutoBackup(switchOn);
            backupMapper.insertSelective(insertBackup);
        } else {
            Backup updateBackup = new Backup();
            updateBackup.setId(backup.getId());
            updateBackup.setAutoBackup(switchOn);
            backupMapper.updateByPrimaryKeySelective(updateBackup);
        }

        AutoBackupVO autoBackupVO = AutoBackupVO.builder()
                .isAutoBackup(switchOn)
                .backupServer(backupProperties.getServer())
                .backupDir(backupProperties.getDir())
                .build();
        if (backup != null && backup.getLatestBackupTime() != null) {
            autoBackupVO.setLatestBackupDate(DateUtil.format(backup.getLatestBackupTime(), "yyyy/MM/dd HH:mm"));
        }
        return autoBackupVO;
    }

    @Override
    public void manualBackup() {
        if (isRecover || isBackup) {
            throw new GlobalException("当前无法手动备份：有备份或恢复正在进行中");
        }

        isBackup = true;

        taskExecutor.execute(() -> {
            FileUtil.mkdir(new File("backup"));

            String mysqlDumpCmd = String.format("mysqldump -u%s -p%s %s --ignore-table=%s.backup --result-file=%s", username, password,
                    backupProperties.getDatabase(), backupProperties.getDatabase(), new File("backup/backup.sql").getAbsolutePath());
            try {
                Process proc = Runtime.getRuntime().exec(mysqlDumpCmd);
                proc.waitFor();

                BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    log.warn(line);
                }
            } catch (Exception e) {
                log.error("mysqldump error", e);
            }

            String tarCmd = "tar -czf ./backup/backup.tar.gz ./resources";
            try {
                Process proc = Runtime.getRuntime().exec(tarCmd);
                proc.waitFor();
            } catch (Exception e) {
                log.error("tar error", e);
            }

            try (SshUtil sshUtil = SshUtil.newInstance(backupProperties.getUsername(), backupProperties.getPassword(),
                    backupProperties.getServer(), 22)) {
                String now = DateUtil.format(new Date(), "yyyyMMddHHmmss");

                sshUtil.upload(new File("backup/backup.sql").getAbsolutePath(),
                        backupProperties.getDir() + "/backup" + now + ".sql");
                sshUtil.upload(new File("backup/backup.tar.gz").getAbsolutePath(),
                        backupProperties.getDir() + "/backup" + now + ".tar.gz");

                sshUtil.exec("cd " + backupProperties.getDir() + " && rm -rf `ls -t|tail -n +7`");

                Backup backup = backupMapperHelper.getBackup();
                if (backup != null) {
                    Backup updateBackup = new Backup();
                    updateBackup.setId(backup.getId());
                    updateBackup.setLatestBackupTime(new Date());
                    updateBackup.setLatestStr(now);
                    backupMapper.updateByPrimaryKeySelective(updateBackup);
                }

            } catch (Exception e) {
                log.error("备份失败", e);
            } finally {
                FileUtil.del(new File("backup/backup.sql"));
                FileUtil.del(new File("backup/backup.tar.gz"));
            }

            isBackup = false;
        });
    }

    @Override
    public void recoverBackup() {
        if (isRecover || isBackup) {
            throw new GlobalException("当前无法手动恢复：有备份或恢复正在进行中");
        }

        Backup backup = backupMapperHelper.getBackup();
        if (backup == null || StringUtils.isBlank(backup.getLatestStr())) {
            throw new GlobalException("当前无法手动恢复：数据库记录不存在");
        }

        isRecover = true;

        taskExecutor.execute(() -> {
            try (SshUtil sshUtil = SshUtil.newInstance(backupProperties.getUsername(), backupProperties.getPassword(),
                    backupProperties.getServer(), 22)) {
                sshUtil.download(backupProperties.getDir() + "/backup" + backup.getLatestStr() + ".sql",
                        new File("backup/backup.sql").getAbsolutePath());
                sshUtil.download(backupProperties.getDir() + "/backup" + backup.getLatestStr() + ".tar.gz",
                        new File("backup/backup.tar.gz").getAbsolutePath());

                String mysqlCmd = String.format("mysql -u%s -p%s %s < %s", username, password,
                        backupProperties.getDatabase(), new File("backup/backup.sql"));
                try {
                    String[] cmdArray = new String[]{"/bin/bash", "-c", mysqlCmd};
                    Process proc = Runtime.getRuntime().exec(cmdArray);
                    proc.waitFor();

                    BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        log.warn(line);
                    }
                } catch (Exception e) {
                    log.error("mysql error", e);
                }

                FileUtil.del(new File("resources"));

                String tarCmd = "tar -xzf ./backup/backup.tar.gz";
                try {
                    Process proc = Runtime.getRuntime().exec(tarCmd);
                    proc.waitFor();
                } catch (Exception e) {
                    log.error("tar error", e);
                }

                Backup updateBackup = new Backup();
                updateBackup.setId(backup.getId());
                updateBackup.setLatestRecoverTime(new Date());
                backupMapper.updateByPrimaryKeySelective(updateBackup);

            } catch (Exception e) {
                log.error("恢复失败", e);
            } finally {
                FileUtil.del(new File("backup/backup.sql"));
                FileUtil.del(new File("backup/backup.tar.gz"));
            }

            isRecover = false;
        });
    }
}
